package net.jxta.myjxta.util.objectmodel;

import net.jxta.id.ID;
import net.jxta.myjxta.ui.model.JxtaTreeModelListener;
import net.jxta.myjxta.util.Constants;
import net.jxta.myjxta.util.Group;
import java.util.*;


public final class MyJxtaObjectRepository
{
    /**
     * Factory to get the one and only ObjectRepository Instance
     * @return
     */
    public static MyJxtaObjectRepository getObjectRepository() {
        if (g_theInstance==null){
            return new MyJxtaObjectRepository();
        }
        return g_theInstance;
    }

    private ArrayList<JxtaTreeModelListener> listeners = null;
    private final JxtaNode m_jxtaRootNode=new JxtaNode(null, Constants.ROOT_NODE);
    private final MyJxtaNode m_rootNode;
    private static final MyJxtaObjectRepository g_theInstance=getObjectRepository();
    final HashMap<JxtaNode, MyJxtaNode> m_object2Node=new HashMap<JxtaNode, MyJxtaNode>();

    /**
     * Create a new JxtaModel, which has a root JXtaNode with
     * a label of "JXTA"
     */
    private MyJxtaObjectRepository() {
        m_rootNode=new MyJxtaNode(null,m_jxtaRootNode);
        m_object2Node.put(m_jxtaRootNode,m_rootNode);
    }


    /**
     * finds the Wrapper that holds the original JxtaNode
     * @param p_jn
     * @return
     */
    private MyJxtaNode getNetworkNode(JxtaNode p_jn) {
        return m_object2Node.get(p_jn);
    }



    /**
     * Add a new JxtaNode to be displayed in the tree
     *
     * @param node the node to add
     * @return true if the node was added, false if the node already
     *         existed in the tree
     */
    
    public boolean add(JxtaNode node,JxtaNode parent) {
        boolean isAdded = false;
        if (parent==null){
            parent=m_jxtaRootNode;
        }
        synchronized(g_theInstance){
        	
            isAdded=addTreeNode(parent,node);
        }
        return isAdded;
    }
        
    /**
     * Remove the indicated node
     *
     * @param node the JxtaNode to remove
     */
    public void remove(JxtaNode node) {
        MyJxtaNode nodeToRemove= getNetworkNode(node);
        if (nodeToRemove!=null){

            List dependantChildren=nodeToRemove.getChildrenAsList();
            for (Iterator iterator = dependantChildren.iterator(); iterator.hasNext();) {
                MyJxtaNode o = (MyJxtaNode) iterator.next();
                remove(o.getData()); //remove children recursive
            }



            boolean removed=nodeToRemove.getParent().getChildrenAsList().remove(nodeToRemove);
            if (!removed){
                throw new IllegalStateException("tried to remove nonexisting node!");
            }
            m_object2Node.remove(node);
            for (Iterator l = getListeners(); l.hasNext(); ) {
                ((JxtaTreeModelListener)l.next()).nodeRemoved(node);
            }
        }
    }
           
//    /**
//     * The indicated Tree node has changed
//     */
    public void nodeChanged(JxtaNode node) {
        node.informAccessed();
        for (Iterator l = getListeners(); l.hasNext(); ) {
                ((JxtaTreeModelListener)l.next()).nodeUpdated(node);
        }
    }

    public JxtaNode getParent(JxtaNode p_jn) {
        MyJxtaNode networkNode= getNetworkNode(p_jn);
        if (networkNode!=m_rootNode){
            return (networkNode!=null)?networkNode.getParent().getData():null;
        } else {
            return null;
        }
    }

    /**
     * Get the number of children of the indicated node
     *
     * @param node the object for which to get the number of children
     */
    public int getChildCount(JxtaNode node) {
        MyJxtaNode networkNode=getNetworkNode(node);
        if (networkNode!=null){
            return networkNode.getChildren().length;
        } else{
            return 0;
        }
    }
    
    /**
     * Get the child of the indicated node at the indicated index
     *
     * @param o the  node from which to get the child
     * @param i the index of the desired child
     *
     * @return the child at the indicated index at the indicated node
     */
    public JxtaNode getChild(JxtaNode o, int i) {
        MyJxtaNode networkNode=getNetworkNode(o);
        if (networkNode!=null){
            return networkNode.getChildren()[i].getData();
        } else {
            return null;
        }
    }

    public String getNormalizedPath(JxtaNode node) {
        if (getNetworkNode(node)==null) return null;

        //suboptimal
        return node.getNormalizedPath();
    }
    
    public JxtaNode getNodeFromNormalizedPath(String path) {
        List<String> l = new ArrayList<String>();
        int i = 0;
        int j = i;
        StringBuffer b = null;
        
        while (j > -1 &&
            (i = path.indexOf(MyJxtaNode.DELIMITER, j)) > -1) {
            j = path.indexOf(MyJxtaNode.DELIMITER, i + 1);

            if (j > -1) {
                if (path.charAt(j - 1) != MyJxtaNode.ESCAPE) {
                    if (b == null) {
                        b = new StringBuffer();
                    }
                    
                    b.append(path.substring(b.length() == 0 ? i + 1 : i, j));
                    l.add(b.toString());

                    b = null;
                } else {
                    if (b == null) {
                        b = new StringBuffer();
                    }
                    
                    b.append(path.substring(i + 1, j - 1));
                }
            } else {
                if (b == null) {
                    b = new StringBuffer();
                }
                
                b.append(path.substring(b.length() == 0 ? i + 1 : i));
                l.add(b.toString());
            }
        }

        String id = null;
        MyJxtaNode n = null;
        MyJxtaNode p = null;
        
        for (Iterator<String> ni = l.iterator(); ni.hasNext(); ) {
            id = ni.next();
            
            if (n == null) {
                n = m_rootNode;
            }
            if (! n.getId().equals(id)) {
                p = null;


                Iterator it=n.getChildrenAsList().iterator();
                for (Iterator c = it;
                    c!=null && c.hasNext() && p == null; ) {
                    p = (MyJxtaNode)c.next();
                    
                    if (! p.getId().equals(id)) {
                        p = null;
                    }
                }
                
                n = p;
                
                if (n == null) {
                    break;
                }
            }
        }
        if (n!=null){
            return n.getData();
        } else {
            return null;
        }
    }

    public static JxtaNode getRoot() {
        return getObjectRepository().m_rootNode.getData();
    }

    public void addListener(JxtaTreeModelListener l) {
        if (this.listeners == null ||
            ! this.listeners.contains(l)) {
            if (this.listeners == null) {
                this.listeners = new ArrayList<JxtaTreeModelListener>();
            }
        
            this.listeners.add(l);
        }
    }
    
    public Iterator getListeners() {
        return this.listeners != null ?
            this.listeners.iterator() : Collections.EMPTY_LIST.iterator();
    }
    
    public JxtaTreeModelListener removeListener(JxtaTreeModelListener l) {
        int i = this.listeners != null ? this.listeners.indexOf(l) : -1;

        return i > -1 ?
            (JxtaTreeModelListener)this.listeners.remove(i) : null;
    }
    
//    /**
//     * Find the group node to which we want to add this Group instance
//     *
//     * @param g the group for which to locate the parent GroupNode
//     * @return the parent GroupNode
//     */
//
/*    	public static MyJxtaNode getParentNode(GroupNode g) {
        JxtaNode n = null;

        MyJxtaNode networkNode = getObjectRepository().getNetworkNode(g);
        if (networkNode!=null){
           return ( networkNode);
        }

        Group group = g.getGroup();
        Group parentGroup = group.getParentGroup();
        if (parentGroup==null){ //must be the NetPeerGroup and this is in the storage net --> illegal state
            throw new IllegalStateException("the given group does nothave a parent group!");
        }

        return getParentNode(new GroupNode(parentGroup));
    }*/
    
    /**
     * Add a child node to the indicated parent
     *
     * @param parent the node to which  to add the child
     * @param child the child to add
     * @return true if the node was added to the tree, false
     *         if the node already existed in the tree
     */
    
    private boolean addTreeNode(JxtaNode parent, JxtaNode child) {
        MyJxtaNode networkParentNode= getNetworkNode(parent);
        if (networkParentNode==null){
            String errorMsg="parent node does not exist! parent:"+parent.toString()+" child:"+child.toString();
            throw new IllegalArgumentException(errorMsg);
        }

        MyJxtaNode[] networkChildNodes=networkParentNode.getChildren();
        for (int i = 0; i < networkChildNodes.length; i++) {
            MyJxtaNode networkChildNode = networkChildNodes[i];
            if (networkChildNode.getData().equals(child)){
                return false;   //node is already there! do nothing
            }
        }

        MyJxtaNode newRepositoryNode=createNewChildNode(networkParentNode,child);
        for (Iterator l = getListeners(); l.hasNext(); ) {
            ((JxtaTreeModelListener)l.next()).nodeAdded(newRepositoryNode.getData());
        }
        return true;
    }

    /**
     * This one actually created the internal node and ensures the parent child integrity
     * @param p_networkParentNode
     * @param p_child
     * @return the new child node
     */
    private MyJxtaNode createNewChildNode(MyJxtaNode p_networkParentNode, JxtaNode p_child) {
        MyJxtaNode newChildNode=new MyJxtaNode(p_networkParentNode,p_child);
        p_networkParentNode.addChild(newChildNode);
        m_object2Node.put(p_child,newChildNode);
        return newChildNode;
    }

    public PeerNode findPeerNodeInGroup(GroupNode p_group,String peerName) {
        MyJxtaObjectRepository.MyJxtaNode networkNode = getNetworkNode(p_group);
        if (networkNode==null){
            return null;
        }
        
        List childs=networkNode.getChildrenAsList();
        for (int i = 0; i < childs.size(); i++) {
            JxtaNode jxtaNode = (JxtaNode) childs.get(i);
            if (jxtaNode instanceof PeerNode){
                if (((PeerNode)jxtaNode).getPeer().getName().equals(peerName)){
                    return (PeerNode) jxtaNode;
                }
            }
        }
        return null;
    }

    public GroupNode findGroupNode(ID p_group_id) {
        Set<JxtaNode> keys=m_object2Node.keySet();
        Iterator<JxtaNode> keyIt=keys.iterator();
        while (keyIt.hasNext()) {
            JxtaNode node = keyIt.next();
            if (node instanceof GroupNode) {
                GroupNode groupNode = (GroupNode) node;
                if (groupNode.getGroup().getId().equals(p_group_id.toString())){
                    return groupNode;
                }
            }
        }
        return null;
    }


    /**
     * This is the internal class used to store the original payload (its private and must not be used outside this class
     */
    private final class MyJxtaNode{
        private final MyJxtaNode m_parentNode;
        private final ArrayList<MyJxtaNode> m_children=new ArrayList<MyJxtaNode>();
        private final JxtaNode m_data;
        public static final String DELIMITER = "/";
        public static final char ESCAPE = '\\';
        private final String m_id;

        private String getId() {
            return m_id;
        }

        public MyJxtaNode[] getChildren(){
            return m_children.toArray(new MyJxtaNode[0]);
        }
        private ArrayList getChildrenAsList(){
            return m_children;
        }
        public MyJxtaNode getParent(){
            return m_parentNode;
        }

        public JxtaNode getData(){
            return m_data;
        }

        /**
         * Protected Constructor - use the Repository factory functions to create a node
         * @param p_data
         */
        MyJxtaNode(MyJxtaNode p_parent,JxtaNode p_data) {
            if (p_parent==null && p_data!=m_jxtaRootNode){
                throw new IllegalArgumentException("parent==null");
            }
            m_data=p_data;
            m_parentNode=p_parent;
            m_id=p_data.getId();
        }

        /**
         * protected - use the @link MyJxtaObjectRepository.addChild(..) functions to add children
         * @param p_child
         */
        void addChild(MyJxtaNode p_child){
            m_children.add(p_child);
        }
    }

}
